package smartlifev30.com.sdk.net.core;

import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;


import java.util.List;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;


/**
 * @param <Request>
 * @param <Response>
 * @author tys
 */
public abstract class BaseNettyClient<Request, Response> implements IClient<Request, Response> {
    private volatile ConnectStatus connectStatus = ConnectStatus.CONNECT_FAILED;
    private Bootstrap bootstrap;
    private NioEventLoopGroup workGroup;
    private ChannelFuture channelFuture;


    /**
     * 连接超时时间(s)
     */
    private int connectTimeOut = 15;

    /**
     * 读事件间隙,指定时间内未发生读事件将触发{@link UserEventTriggeredListen#readerIdle(ChannelHandlerContext)}
     * 0表示未设置,用于心跳设置.
     */
    private int readTimeOut = 0;

    /**
     * 写事件间隙,指定时间内未发生写事件将触发{@link UserEventTriggeredListen#writerIdle(ChannelHandlerContext)}
     * 0表示未设置,用于心跳设置
     */
    private int writeTimeOut = 0;

    /**
     * 读,写事件间隙,指定时间内未发生读写事件将触发{@link UserEventTriggeredListen#allIdle(ChannelHandlerContext)}
     * 0表示未设置,用于心跳设置
     */
    private int allTimeOut = 0;

    /**
     * 用户事件, 用于心跳设置(不要忘了设置对应事件的时间比如：{@link #writeTimeOut})
     */
    private UserEventTriggeredListen userEventTriggeredListen;

    /**
     * 网络状态改变回到。依赖于Netty的{@link NettyClientHandler#channelActive(ChannelHandlerContext)}和{@link NettyClientHandler#channelInactive(ChannelHandlerContext)}
     */
    private ConnectStatusChangeListen statusChangeListen;


    protected BaseNettyClient() {
        init();
    }

    private void init() {
        bootstrap = new Bootstrap();
        workGroup = new NioEventLoopGroup();
        bootstrap.channel(NioSocketChannel.class).group(workGroup).option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeOut * 1000)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline()
                                .addLast(new IdleStateHandler(readTimeOut, writeTimeOut, allTimeOut))
                                .addLast(new Encoder())
                                .addLast(new Decoder())
                                .addLast(new NettyClientHandler(BaseNettyClient.this));
                    }
                });
    }


    protected void connect(@NonNull String host, @NonNull Integer port) {
        this.connect(host, port, null);
    }

    @Override
    public void connect(@NonNull String host, @NonNull Integer port, ConnectListen listen) {
        if (getConnectStatus() == ConnectStatus.CONNECT_ING) {
            return;
        }

        if (TextUtils.isEmpty(host) || port <= 0) {
            throw new IllegalArgumentException("host or port is incorrect, please check host or port");
        }
        connectStatusChange(ConnectStatus.CONNECT_ING);
        ChannelFuture channelFuture = bootstrap.connect(host, port);
        channelFuture.addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                this.channelFuture = future;
                if (listen != null) {
                    listen.onSuccess();
                }
            } else {
                connectStatusChange(ConnectStatus.CONNECT_FAILED);
                if (listen != null) {
                    listen.onFailed(future.cause());
                }
                future.channel().close();
            }
        });
    }

    @Override
    public void disConnect() {
        if (channelFuture != null) {
            channelFuture.channel().close();
        }
        if (workGroup != null) {
            workGroup.shutdownGracefully();
            workGroup = null;
        }
        bootstrap = null;
    }

    @Override
    public void send(Request request) {
        if (channelFuture != null) {
            channelFuture.channel().writeAndFlush(request);
        }
    }

    @Override
    public ConnectStatus getConnectStatus() {
        return connectStatus;
    }

    public int getConnectTimeOut() {
        return connectTimeOut;
    }

    public void setConnectTimeOut(int connectTimeOut) {
        this.connectTimeOut = connectTimeOut;
    }

    public int getReadTimeOut() {
        return readTimeOut;
    }

    public void setReadTimeOut(int readTimeOut) {
        this.readTimeOut = readTimeOut;
    }

    public int getWriteTimeOut() {
        return writeTimeOut;
    }

    public void setWriteTimeOut(int writeTimeOut) {
        this.writeTimeOut = writeTimeOut;
    }

    public int getAllTimeOut() {
        return allTimeOut;
    }

    public void setAllTimeOut(int allTimeOut) {
        this.allTimeOut = allTimeOut;
    }

    public UserEventTriggeredListen getUserEventTriggeredListen() {
        return userEventTriggeredListen;
    }

    public void setUserEventTriggeredListen(UserEventTriggeredListen userEventTriggeredListen) {
        this.userEventTriggeredListen = userEventTriggeredListen;
    }

    public ConnectStatusChangeListen getStatusChangeListen() {
        return statusChangeListen;
    }

    public void setStatusChangeListen(ConnectStatusChangeListen statusChangeListen) {
        this.statusChangeListen = statusChangeListen;
    }

    protected void connectStatusChange(ConnectStatus connectStatus) {
        Log.e("0-0-", "狀態改變:" + connectStatus);
        this.connectStatus = connectStatus;
        if (statusChangeListen != null) {
            statusChangeListen.onStatusChange(connectStatus);
        }
    }

    /**
     * 网络断开， 编解码异常
     *
     * @param ctx
     * @param cause
     */
    protected void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 用户事件,用于心跳设置
     *
     * @param ctx
     * @param evt
     */
    protected void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        if (evt instanceof IdleStateEvent) {
            IdleState state = ((IdleStateEvent) evt).state();
            switch (state) {
                case ALL_IDLE:
                    if (userEventTriggeredListen != null) {
                        userEventTriggeredListen.allIdle(ctx);
                    }
                    break;
                case READER_IDLE:
                    if (userEventTriggeredListen != null) {
                        userEventTriggeredListen.readerIdle(ctx);
                    }
                    break;
                case WRITER_IDLE:
                    if (userEventTriggeredListen != null) {
                        userEventTriggeredListen.writerIdle(ctx);
                    }
                    break;
                default:
            }
        }
    }

    private class NettyClientHandler extends ChannelInboundHandlerAdapter {

        private final BaseNettyClient<Request, Response> client;

        NettyClientHandler(BaseNettyClient<Request, Response> client) {
            this.client = client;
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            client.connectStatusChange(ConnectStatus.CONNECT_SUCCEED);
            super.channelActive(ctx);
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            client.connectStatusChange(ConnectStatus.CONNECT_FAILED);
            super.channelInactive(ctx);
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            client.receive((Response) msg);
            super.channelRead(ctx, msg);
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            client.userEventTriggered(ctx, evt);
            super.userEventTriggered(ctx, evt);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            super.exceptionCaught(ctx, cause);
            client.exceptionCaught(ctx, cause);
        }

    }


    public interface UserEventTriggeredListen {
        void readerIdle(ChannelHandlerContext ctx);

        void writerIdle(ChannelHandlerContext ctx);

        void allIdle(ChannelHandlerContext ctx);
    }

    /**
     * 连接状态改变
     */
    public interface ConnectStatusChangeListen {
        /**
         * Activity : {@link ConnectStatus#CONNECT_SUCCEED}
         * InActivity : {@link ConnectStatus#CONNECT_FAILED}
         * {@link BaseNettyClient#connect}后进入{@link ConnectStatus#CONNECT_ING}
         *
         * @param connectStatus
         */
        void onStatusChange(ConnectStatus connectStatus);
    }

    private class Decoder extends ByteToMessageDecoder {
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            byte[] bytes = new byte[in.readableBytes()];
            in.readBytes(bytes);
            Response response = decoder(bytes);
            out.add(response);
        }
    }

    private class Encoder extends MessageToByteEncoder<Request> {
        @Override
        protected void encode(ChannelHandlerContext ctx, Request msg, ByteBuf out) throws Exception {
            byte[] bytes = encoder(msg);
            Log.e("0-0-", new String(bytes));
            out.writeBytes(bytes);
        }
    }

    /**
     * 解码 byte ==> Response 用于{@link #receive(Object)}
     *
     * @param bytes
     * @return
     */
    protected abstract Response decoder(byte[] bytes);

    /**
     * 编码Request ==> byte  用于{@link #send(Object)}
     *
     * @param msg
     * @return
     */
    protected abstract byte[] encoder(Request msg);


    /**
     * 收到信息后回调
     *
     * @param response 其具体类型依赖于{@link #decoder(byte[])}
     */
    @Override
    public abstract void receive(Response response);
}
